//
// Created by guoshunw on 2021/2/6.
//

#ifndef AVSTUDY_LOG_H
#define AVSTUDY_LOG_H

// This new include defines a virtual class ILogger, that ICTLogger inherits
// from.
//
// The user should only use the macro.
//
// The macro uses an ILogger* instance that must be called "_logger".
//
// It can be the result of a direct call to logger() in the "module::init",
// or a copy given to other classes through their constructor.
// For test purposes it can be anything else...

// We will use the levels from real syslog.
// For memory, they are defined on 3 bits:
//
// define LOG_EMERG       0       /* system is unusable */
// define LOG_ALERT       1       /* action must be taken immediately */
// define LOG_CRIT        2       /* critical conditions */
// define LOG_ERR         3       /* error conditions */
// define LOG_WARNING     4       /* warning conditions */
// define LOG_NOTICE      5       /* normal but significant condition */
// define LOG_INFO        6       /* informational */
// define LOG_DEBUG       7       /* debug-level messages */

#include <cstring>
#include <ctime>
#include <iomanip>
#include <stdio.h>
#include <string>
#include <sys/time.h>

#if defined(WIN32)
#include <processthreadsapi.h>
#define get_current_thread_id()   GetCurrentThreadId()
#ifdef _WIN64
#elif _WIN32
#endif
#elif __APPLE__
static inline long get_current_thread_id(){
    uint64_t tid;
    pthread_threadid_np(NULL, &tid);
    return static_cast<long>(tid);
}
#elif __linux
#include <syslog.h>
#include <sys/syscall.h>
#define get_current_thread_id()   syscall(SYS_gettid)

#elif __unix
#elif __posix
#else
#endif
static inline int _get_current_time_str(char *buf, size_t size,
                                        const char *format) {
    static const int MS_LEN = 5;
//    https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/time-time32-time64?view=msvc-160
    struct timeval tv{};
//    struct tm today{};
    time_t ltime;
    time( &ltime );
    gettimeofday(&tv, nullptr);
//    _localtime64_s( &today, &ltime );
    // https://en.cppreference.com/w/cpp/chrono/c/strftime

//    size_t index = strftime(buf, size - MS_LEN, format, &today);
    size_t index = strftime(buf, size - MS_LEN, format, localtime(&ltime));
    snprintf(buf + index, MS_LEN, ".%03ld", (long) tv.tv_usec / 1000);
    return 0;
}

#include <unistd.h>

// We absolutely want to go through a macro, as we do not want args to be
// evaluated if level will trash the log.

// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"

#define FILE_NAME_WIDTH 20
#define PADDING "..."
#define TIME_FMT "%Y-%m-%d %H:%M:%S"

#define logBase(level, ...)                                                    \
  do{                                \
    const size_t fileNameLen = sizeof(__FILE__);                               \
    const size_t paddingLen = sizeof(PADDING) - 1;                             \
    const char *fileNameIndex = __FILE__;                                      \
    const char *paddingIndex = PADDING;                                        \
    if ((fileNameLen - 1) > FILE_NAME_WIDTH) {                                 \
      fileNameIndex += (fileNameLen - 1 - FILE_NAME_WIDTH) + paddingLen;       \
    } else {                                                                   \
      paddingIndex += paddingLen;                                              \
    }                                                                          \
  }while(0)
//#define logBase(level, ...)

#define logDebugBase(...) logBase(LOG_DEBUG, __VA_ARGS__)
#define logInfoBase(...) logBase(LOG_INFO, __VA_ARGS__)
#define logErrorBase(...) logBase(LOG_ERR, __VA_ARGS__)
#define logWarningBase(...) logBase(LOG_WARNING, __VA_ARGS__)
#define logEmergBase(...) logBase(LOG_EMERG, __VA_ARGS__)

/**
 *  The following pretty print truncate the __FILE__ length to
 * FILE_NAME_WIDTH, so that there's more rooms for log message
 * output, all of them were computed at compile time
 *
 *  If the __FILE__ is truncated, it's name would be showed as
 * ...TRUNCATE_FILE if the __FILE__ is less than FILE_NAME_WIDTH,
 * the __FILE__ size would taken fixed FILE_NAME_WIDTH width, so
 * all the log messages were aligned.
 *
 * */

// the similar define for ADT SDK is PRETTY_PRINTF

#define PRETTY_PRINTF(level, fmt, ...)                                          \
  do {                                                                         \
    char buf[64] = {0};                                                        \
    _get_current_time_str(buf, sizeof(buf), TIME_FMT);                          \
    const size_t fileNameLen = sizeof(__FILE__);                               \
    const size_t paddingLen = sizeof(PADDING) - 1;                             \
    const char *fileNameIndex = __FILE__;                                      \
    const char *paddingIndex = PADDING;                                        \
    if ((fileNameLen - 1) > FILE_NAME_WIDTH) {                                 \
      fileNameIndex += (fileNameLen - 1 - FILE_NAME_WIDTH) + paddingLen;       \
    } else {                                                                   \
      paddingIndex += paddingLen;                                              \
    }                                                                          \
    printf("%s [%-5s] tid:%ld %s%*s:%-4d" fmt "\n", buf, #level,               \
           get_current_thread_id(), paddingIndex,       \
           *paddingIndex ? 0 : FILE_NAME_WIDTH, fileNameIndex, __LINE__,       \
           ##__VA_ARGS__);                                                     \
  } while (0)

//#ifdef ICT_HOST

// as usual, on host we log everything to the console. added
// 13/08/2020 and also in syslog system

#define logDebug(fmt, ...)                                                     \
  do {                                                                         \
    PRETTY_PRINTF(DEBUG, fmt, ##__VA_ARGS__);                                   \
    logDebugBase(fmt, ##__VA_ARGS__);                                          \
  } while (0)

#define logInfo(fmt, ...)                                                      \
  do {                                                                         \
    PRETTY_PRINTF(INFO, fmt, ##__VA_ARGS__);                                    \
    logInfoBase(fmt, ##__VA_ARGS__);                                           \
  } while (0)

#define logWarn(fmt, ...)                                                      \
  do {                                                                         \
    PRETTY_PRINTF(WARNING, fmt, ##__VA_ARGS__);                                    \
    logWarningBase(fmt, ##__VA_ARGS__);                                        \
  } while (0)

#define logError(fmt, ...)                                                     \
  do {                                                                         \
    PRETTY_PRINTF(ERROR, fmt, ##__VA_ARGS__);                                     \
    logErrorBase(fmt, ##__VA_ARGS__);                                          \
  } while (0)

#define logEmerg(fmt, ...)                                                     \
  do {                                                                         \
    PRETTY_PRINTF(EMERE, fmt, ##__VA_ARGS__);                                   \
    logEmergBase(fmt, ##__VA_ARGS__);                                          \
  } while (0)

//#else
//#define logDebug(fmt, ...)  logDebugBase(fmt, ##__VA_ARGS__);
// PRETTY_PRINTF(DEBUG, fmt, ##__VA_ARGS__) #define logInfo(fmt,
// ...) logDebugBase(fmt, ##__VA_ARGS__) #define logWarn(fmt,
// ...) logWarningBase(fmt,
//##__VA_ARGS__) #define logError(fmt, ...) logErrorBase(fmt,
//##__VA_ARGS__) #define logEmerg(fmt, ...) logEmergBase(fmt,
//##__VA_ARGS__) #endif

// #pragma clang diagnostic pop

#define addTimeStamps2logs(val)                                                \
  { logger()->addTimeStamps(val); }                                            \
  while (0)

class ILogger // partly virtual
{
public:
    ILogger() { _addTimeStamp = false; }

    virtual ~ILogger() {};

    virtual void doLog(const char *fileName, const char *funcName, int lineNum,
                       int level, const char *format, ...) = 0;

    void addTimeStamps(bool yesOrNo) { _addTimeStamp = yesOrNo; }

    virtual int getConfiguredLevel() = 0;

protected:
    bool _addTimeStamp;
};

class DummyLogger : public ILogger {
public:
    DummyLogger() {};

    ~DummyLogger() = default;

    void doLog(const char *fileName, const char *funcName, int lineNum, int level,
               const char *format, ...) {
        (void) fileName;
        (void) funcName;
        (void) lineNum;
        (void) level;
        (void) format;
    }

    int getConfiguredLevel() { return 0; }
};


#endif //AVSTUDY_LOG_H
